var colors = require('colors');
var path = require('path');
var httpProxy = require('http-proxy');
var proxyServer = httpProxy.createProxyServer({});
var moment = require('moment');
var logger = require('tracer').colorConsole();
var request = require('request');
var fs = require('fs');
var _ = require('lodash');
var resourceProperty = require('./config.js');

var url = require('url');
var formidable = require('formidable');

var write = require('hy5b/backend/write')

// To modify the proxy connection before data is sent, you can listen 
// for the 'proxyReq' event. When the event is fired, you will receive 
// the following arguments: 
// (http.ClientRequest proxyReq, http.IncomingMessage req, 
//  http.ServerResponse res, Object options). This mechanism is useful when 
// you need to modify the proxy request before the proxy connection 
// is made to the target. 
// 
// proxyServer.on('proxyReq', function(proxyReq, req, res, options) {
//   // proxyReq.setHeader('X-Special-Proxy-Header', 'foobar');
//   console.log('cookie:', proxyReq.getHeader('cookie'))
//   console.log('host:', proxyReq.getHeader('host'))
//   console.log('referer:', proxyReq.getHeader('referer'))
//   console.log('origin:', proxyReq.getHeader('origin'))
// });

// 
// Listen for the `proxyRes` event on `proxy`. 
// proxyRes应当是一个 (HTTP responses, on the client)
// 文档上讲 它是一个 http.IncomingMessage 类， 它实现了 可读流(stream.Readable 类) 接口
// 对于可读流，'data' 事件会在流将数据传递给消费者时触发
// 按理说这里应该就是消费掉了，它就应该回不到最终端了，但实际上在浏览器上还是看到了内容
// 所以这里就不懂了
// 
// proxyServer.on('proxyRes', function(proxyRes, req, res) {
//   console.log(proxyRes.statusCode)
//   proxyRes.on('data', function(chunk) {
//     console.log(chunk.toString())
//   })

//   if (0) {
//     delete proxyRes.headers['etag']
//     delete proxyRes.headers['last-modified']
//       //以上要个要全部不存在才有效

//     util.setHeader(res, 'X-Special-Proxy-Header', 'water7melon');

//     util.setHeader(res, 'Cache-Control', 'private, max-age=0, no-cache');
//     util.setHeader(res, 'Pragma', 'no-cache');
//     // 以上两个也没有什么卵用, 但是微信端需要有才有效
//     util.setHeader(res, 'Expires', 'Mon, 26 Jul 1970 05:00:00 GMT'); //并没有什么卵用,没有的话微信端会加载很慢
//   }
// });

var util = {
  cors: function(res) {
    // util.setHeader(res, 'Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Authorization, token, userTokenHeader, tenantCode');
    util.setHeader(res, 'Access-Control-Allow-Headers', 'Content-Type, Authorization, userTokenHeader, *');
    util.setHeader(res, 'Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE, OPTIONS');
    util.setHeader(res, 'Access-Control-Allow-Origin', '*');
    util.setHeader(res, 'Access-Control-Max-Age', '3600');
    util.setHeader(res, 'Allow', 'GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH');
  },
  proxylog: function(req, res, target, resetReqUrl) {
    console.log('[%s] "%s %s %s %s %s %s" "%s"', moment().format('YYYY-MM-DD HH:mm:ss,SSSZ'),
      req.method.green,
      resetReqUrl ? resetReqUrl.yellow.bold : req.url.yellow.bold,
      'pass'.green,
      req.headers.host.yellow.bold,
      'to'.green,
      target.yellow.bold,
      req.headers['user-agent']
    );
  },
  formParse: function(req, cb) {
    var form = new formidable.IncomingForm();
    form.parse(req, function(err, fields, files) {
      cb(err, fields, files);
    });
  },
  store2Mock: function(method, _path, body, _url) {
    try {
      var storePath = path.resolve(__dirname, `../data${_path}#${method}.json`)
      var storeFs = write(storePath);
      storeFs.write(body);
    } catch (e) {
      throw new Error({ message: 'STORE_FILE_ERROR', stack: e.stack })
    }
  },
  proxyMultiPartForm: function(req, res, target) {
    target = target || 'http://127.0.0.1';
    util.proxylog(req, res, target);

    try {
      //不带show的path往带show的path proxy 应该怎么写
      //经测试默认会接上去的
      //那我为什么还要自己写util.proxy?(2018-4-26)
      proxyServer.web(req, res, {
        target: target,
        headers: {
          referer: target,
          origin: target.replace(/\/show/, '') //成功修改origin，告别403
        },
        changeOrigin: true, //修改host但不修改origin
        // hostRewrite: true, //无效
        secure: false
      });
    } catch (e) {
      logger.error(e);
    }
  },
  proxy: function(req, res, target, cb, resetReqUrl) {
    util.proxylog(req, res, target || 'http://127.0.0.1', resetReqUrl);

    var reqUrl = resetReqUrl || req.url; //proxyUrl是去掉了/show的req.url
    var oReqUrl = url.parse(reqUrl);
    var path = oReqUrl.path;
    var pathname = oReqUrl.pathname;
    var targetUrl = (target || 'http://127.0.0.1') + path;

    var refererHostname = oReqUrl.hostname;

    if (!refererHostname) {
      var referer = req.headers.referer;
      var oreferer = url.parse(referer || 'http://' + req.headers.host); //host包含hostname和port，不能直接parse
      refererHostname = oreferer.hostname;
    }

    var headers = {
      referer: targetUrl,
      cookie: req.headers.cookie,
      accept: req.headers.accept,
      "content-type": req.headers['content-type'], //响应头,放在请求里？应该是无效的吧?我们的服务器发POST请求,参数是json必须要这个，应该是后端做得不规范
      'accept-language': req.headers['accept-language'],
      'user-agent': req.headers['user-agent'],
      'Authorization': req.headers['authorization'],
      'userTokenHeader': req.headers['usertokenheader'],
      'token': req.headers['token'],
      'tenantCode': req.headers['tenantcode'],
      // 'accept-encoding': req.headers['accept-encoding']
    };

    if (/^\/tenant\/?$/.test(pathname)) {
      headers.cookie = '' 
    }

    if (req.headers.apikey) {
      headers['apikey'] = req.headers.apikey
    }

    if (req.headers['x-socket-id']) { //for chatops,属性全小写
      headers['x-socket-id'] = req.headers['x-socket-id']
    }

    if (headers['content-type'] === 'text/plain') {
      headers['content-type'] = 'application/json'
    }

    //当使用代理到真实环境时将返回数据写到mock数据
    //这样下次就可以直接用了
    var callback = function(error, response, body) {
      // console.log(44444, body)
      
      try {
        var setCookies = response.headers['set-cookie'];
        // logger.info(77777, setCookies)
        if (setCookies && setCookies.length > 0) {
          var newSetCookies = [];
          for (var i = 0; i < setCookies.length; i++) {
            var setCookie = setCookies[i];
            setCookie = setCookie.replace(/Domain=.*;/i, 'Domain=' + refererHostname + ';')
            if (req.url.indexOf('https:') === -1) { //本地是http
              //把返回的cookies去掉secure标志
              setCookie = setCookie.replace(/secure;/i, '')
            }
            newSetCookies.push(setCookie)
          }
          // console.log(newSetCookies)
          response.headers['set-cookie'] = newSetCookies;
        }

        if (response.headers['content-encoding']) { //?
          delete response.headers['content-encoding']
        }
        response.headers['expries'] = -1;
        response.headers['cache-control'] = 'no-cache'
        response.headers['pragma'] = 'no-cache'
        response.headers['x-frame-options'] = ''
        // logger.info(88888, response.headers)
        util.setHeaders(res, response.headers);
        util.cors(res)
      } catch (e) {}

      if (error) { //网络失败
        logger.error('proxy to ' + targetUrl + ': ' + error + ', ' + body);
        res.statusCode = response ? response.statusCode : 500;
        res.end(JSON.stringify({errCode: res.statusCode, message: targetUrl + ' connect error: ' + error + ', ' + body}));
        return;
      } else if (response.statusCode !== 200) { //协议非200失败
        logger.warn('proxy to ' + targetUrl + ': status code is ' + response.statusCode + ', ' + body);
        res.statusCode = response.statusCode;
        res.end(body);
        return;
      } else {
        if (resourceProperty.AUTO_MOCK_WHEN_PROXY) { //TODO: 判断返回类型，不是文本不要尝试保存
          let content = '';
          if (body instanceof String) {
            content = body;
          } else if (body instanceof Buffer) {
            content = body.toString();
          }
          try {
            var jsonObj = JSON.parse(content);
            if ((!jsonObj.errCode || parseInt(jsonObj.errCode) === 200) && (!jsonObj.code || parseInt(jsonObj.code) === 200)) {
              util.store2Mock(req.method, pathname, JSON.stringify(jsonObj, null, 2), reqUrl);
            }
          } catch (e) {
            // TODO: how to store media resource?
            if (e.message === 'STORE_FILE_ERROR') {
              logger.error(e.stack)
            } else {
              logger.warn('store2Mock error: mismatch json data');
            }
          }
        }
      }

      if (cb) {
        //callback返回true时由callback自己输出到res
        var ret = cb(res, body);
        if (ret !== true) {
          res.end(body);
        }
      } else {
        res.end(body);
      }
    }

    var options = {
      rejectUnauthorized: false,
      url: targetUrl,
      headers: headers,
      // gzip: true,
    }

    if (req.method.toUpperCase() === "GET") {
      request.get(_.merge({}, options, {
        encoding: null
      }), callback);
    } else if (req.method.toUpperCase() === "POST") {
      //不知道为什么, 以前webserver用union的时候需要，用了express后不需要了
      // req.pipe(process.stdin);

      var form = new formidable.IncomingForm();

      // console.log(11111, targetUrl)
      form.parse(req, function(err, fields, files) {
        // console.log(files)
        if (/^application\/json\b/.test(req.headers['content-type'])) {
          // console.log(22222, targetUrl, headers, fields)
          request.post(_.merge({}, options, {
            body: JSON.stringify(fields)
          }), callback);
        } else {
          // console.log(33333, targetUrl, headers, fields)
          if (!_.isEmpty(files)) {
            var formData = _.merge({}, fields);
            //单选图片，多选可能结构不一样
            _.each(files, function(file, name) { //File, param-name
              //https://github.com/request/request#multipartform-data-multipart-form-uploads
              formData[name] = {
                value: fs.createReadStream(file.path),
                options: {
                  filename: file.name,
                  filepath: '/fackpath',
                  contentType: file.type,
                  knownLength: file.size
                }
              }
            })
            request.post(_.merge({}, options, {
              formData: formData // multipart/form-data
            }), callback);
          } else {
            request.post(_.merge({}, options, {
              form: fields
            }), callback);
          }
        }
      });
    } else if (req.method.toUpperCase() === "PUT") {
      var form = new formidable.IncomingForm();
      // console.log(11111, targetUrl)
      form.parse(req, function(err, fields, files) {

        if (/^application\/json\b/.test(req.headers['content-type'])) {
          // console.log(22222, targetUrl, fields)
          request.put(_.merge({}, options, {
            body: JSON.stringify(fields)
          }), callback);
        } else {
          // console.log(33333, targetUrl, fields)
          request.put(_.merge({}, options, {
            form: fields
          }), callback);
        }
      });
    } else if (req.method.toUpperCase() === "DELETE") {
      request.delete(_.merge({}, options, {
        
      }), callback);
    } else if (req.method.toUpperCase() === "OPTIONS") {
      util.cors(res);
      res.end()
    } else {
      logger.error('unknow request method');
      request[req.method.toLowerCase()](_.merge({}, options, {
        
      }), callback);
    }
  },
  paramLog: function(req, res) {
    if (req.method.toUpperCase() === "GET" || req.method.toUpperCase() === "DELETE") {
      // logger.info(req.query)
    } else if (req.method.toUpperCase() === "PUT" || req.method.toUpperCase() === "POST") {
      var form = new formidable.IncomingForm();
      form.parse(req, function(err, fields, files) {
        // logger.info(fields)
      })
    } else {
      logger.error('unknow request method');
    }
  },
  out: function(req, res, o, contentType) {
    util.setHeader(res, 'Content-Type', contentType || 'text/plain; charset=utf-8');
    util.cors(res);
    res.end(JSON.stringify(this.transformOutJson(o)));
  },
  outJson: function(req, res, o) {
    util.out(req, res, o, 'application/json; charset=utf-8');
  },
  transformOutJson: function(o) {
    // var result = {};
    // result.errCode = o.code || 200;
    // (o.msg ? result.message = o.msg : '');
    // (o.data ? result.data = o.data : '');
    // return result;
    return o;
  },
  outJsonp: function(req, res, o) {
    util.setHeader(res, 'Content-Type', 'text/javascript');
    var callback = req.query.callback;
    res.end(callback + "(" + JSON.stringify(this.transformOutJson(o)) + ")");
  },
  setHeader: function(res, key, value) {
    if (key && value) {
      res.set(key, value);
    }
  },
  setHeaders: function(res, headers) {
    if (headers !== undefined) {
      res.set(headers);
    }
  }
}

module.exports = util;